package top.ninwoo.edgecenter.service.impl;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import top.ninwoo.common.entity.ClusterConfig;
import top.ninwoo.common.entity.ContainerDescription;
import top.ninwoo.common.entity.DockerContainer;
import top.ninwoo.edgecenter.service.ClusterService;
import top.ninwoo.edgecenter.service.IpService;
import top.ninwoo.edgecenter.service.TopologyService;
import top.ninwoo.utils.entity.NetworkInfo;
import top.ninwoo.utils.entity.OvsBridge;
import top.ninwoo.utils.service.DockerService;
import top.ninwoo.utils.service.OVSService;
import top.ninwoo.utils.service.OvsDockerService;
import top.ninwoo.utils.service.TcService;
import top.ninwoo.utils.util.OSUtils;

import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author joliu
 * @date 2019-10-18
 * @description 集群服务类
 */
@Service
public class ClusterServiceImpl implements ClusterService {
    private final Logger LOG = LoggerFactory.getLogger(ClusterServiceImpl.class);

    // ConcurrentHashMap<ClusterConfig.id, Map<DockerContainer.name, Set<DockerContainer.id>>> 一个基础容器名，对应多个容器id
    private ConcurrentHashMap<Long, Map<String, Set<String>>> clustersInfo = new ConcurrentHashMap<>();
    @Autowired
    DockerService dockerService;

    @Autowired
    OVSService ovsService;

    // TODO: 这个服务接口可以拆分成两个
    @Autowired
    TcService tcService;

    // TODO: 这个接口临时使用
    @Autowired
    OvsDockerService ovsDockerService;

    @Autowired
    OSUtils osUtils;

    @Autowired
    TopologyService topologyService;

    @Autowired
    IpService ipService;

    /**
     * 定时更新容器列表
     */
    /*@Scheduled(fixedRate = 5000)
    @Override
    public void update() {
        dockerService.updateContainers();
    }*/

    @Override
    public List<DockerContainer> getContainerIds() {
        return getContainerIds(false);
    }

    /**
     * 返回容器的id
     * @param isAll
     * @return
     */
    @Override
    public List<DockerContainer> getContainerIds(boolean isAll) {
        if(isAll) {
            return dockerService.getAllContainers();
        } else {
            return dockerService.getRunningContainers();
        }
    }

    /**
     * ＠description 创建容器组
     * @param clusterConfig
     * @return
     */
    @Override
    public Map<String, Set<String>> initContainers(ClusterConfig clusterConfig) {
        // 校验ClusterConfig是否符合要求
        if(clusterConfig.getId() == 0L) {
            // 需要保存到数据库中，获取全局唯一的集群id
            saveClusterConfig(clusterConfig);
        }
        if(clustersInfo.containsKey(clusterConfig.getId())) {
            clustersInfo.get(clusterConfig.getId()).clear();
        } else {
            clustersInfo.put(clusterConfig.getId(), new HashMap<String, Set<String>>());
        }
        // 启动容器
        List<ContainerDescription> dockers = clusterConfig.getDockers();
        dockers.forEach(c -> {
            // 创建docker的逻辑
            // TODO: 根据Mode选择不同的模式
            // 根据副本数量创建多少个容器
            if(c.getReplicas() <= 0) {
                // do nothing
            } else {
                DockerContainer containerConfig = c.getDockerContainer();
                String containerName = containerConfig.getName();
                // 校验Container名称是否唯一
                if(clustersInfo.get(clusterConfig.getId()).containsKey(containerName)) {
                    throw new RuntimeException("Container Name must be unique!");
                }
                clustersInfo.get(clusterConfig.getId()).put(containerName, new HashSet<>());
                for (int i = 0; i < c.getReplicas(); i++) {
                    // 处理DockerContainer中的名称
                    if(!"".equals(containerConfig.getName())) {
                        containerConfig.setName(containerName+ "_" + i);
                    }
                    DockerContainer dockerContainer = dockerService.runDocker(containerConfig);
                    clustersInfo.get(clusterConfig.getId()).get(containerName).add(dockerContainer.getId());
                }
            }
        });
        clusterConfig.setCreateTime(new Date());
        return clustersInfo.get(clusterConfig.getId());
    }

    // 动态调整容器规模，涉及到网络的一个调整
    @Override
    public String adjustCluster(ClusterConfig clusterConfig) {
        // 这里要求集群的配置在调整之前必须存在
        if(!clustersInfo.containsKey(clusterConfig.getId())) {
            return "集群配置不存在，请先初始化";
        }

        // 拿到当前的集群情况
        Map<String, Set<String>> apps = clustersInfo.get(clusterConfig.getId());
        // 与新的配置文件进行一一比较
        // TODO:这里实际上应该访问数据库，对比数据库中的配置数据
        List<ContainerDescription> dockers = clusterConfig.getDockers();

        for (ContainerDescription docker : dockers) {
            String appName = docker.getDockerContainer().getName();
            if(!apps.containsKey(appName)) {
                // 如果不包含这个app的名称，暂时不支持创建新的节点
                return "暂不支持创建新的应用集群！";
            }
            // 对比docker的容器数量
            int change = docker.getReplicas() - apps.get(appName).size();
            // 执行扩缩容
            updateContainerScale(docker.getDockerContainer(), change, apps.get(appName), clusterConfig.getId());
        }
        return "success";
    }

    public void updateContainerScale(DockerContainer dockerContainer, int change, Set<String> cids, Long clusterId) {
        // 备份原来的容器名称
        String name = dockerContainer.getName();
        if(change > 0) {
            // 扩容
            for (int i = 0; i < change; i++) {
                // 先修改容器的名称
                dockerContainer.setName(name + "_" + cids.size());
                DockerContainer newContainer = dockerService.runDocker(dockerContainer);
                cids.add(newContainer.getId());
                dockerService.addNewContainerId(newContainer);
                // 创建相同的网络
                topologyService.createLinkForNewer(clusterId, name, newContainer.getId());
            }
        } else if(change < 0) {
            // 缩容
            int size = cids.size();
            for (int i = 0; i < (-change); i++) {
                // step1 通过容器名称进行删除
                String cName = name + "_" + (size - 1 - i);
                String cid = dockerService.deleteDockerByName(cName);
                // 从容器列表中移除
                cids.remove(cid);
                // step 2 删除ovs上多余的网络
                ovsDockerService.deleteContainerPorts(cid);
                // step 3 删除对应的ip地址
                String ip = ipService.getContainerIp(cid);
                ipService.deleteIP(clusterId, name, cid, ip);

            }
        }
        // 把docker容器配置恢复初始状态
        dockerContainer.setName(name);
    }

    /**
     * @description 保存配置文件到数据库
     * @param clusterConfig
     */
    public ClusterConfig saveClusterConfig(ClusterConfig clusterConfig) {
        // TODO: 保存数据到数据库
        clusterConfig.setId(11111L);
        // TODO: 在Zookeeper上注册对应的cluster服务，以便云端开启监听
        return clusterConfig;
    }

    /**
     * @description 获取集群Id集合
     * @return
     */
    @Override
    public Set<Long> getClusterIds() {
        return clustersInfo.keySet();
    }

    /**
     * @description 获取集群中的指定容器的容器组集合
     * @param clusterId
     * @param containerName
     * @return
     */
    @Override
    public Set<String> getContainerIdsByClusterId(long clusterId, String containerName) {
        // 做安全判断
        if(clustersInfo.containsKey(clusterId)) {
            return clustersInfo.get(clusterId).get(containerName);
        }
        return new HashSet<>();
    }

    /**
     * @description 删除指定集群的全部容器
     * @param clusterId
     */
    @Override
    public void removeContainersByClusterId(long clusterId) {
        if(!clustersInfo.containsKey(clusterId)) {
            return;
        }
        clustersInfo.get(clusterId).keySet().forEach(
                //c -> removeContainersByClusterIdAndContainerName(clusterId, c)
                // bug: 遍历过程中，不能进行删除，会导致并发错误，不能直接调用removeContainersByCo...接口
                cName -> {
                    LOG.debug("删除应用[" + cName + "]");
                    // 获取全部的容器id
                    clustersInfo.get(clusterId).get(cName).forEach(
                            c -> {
                                // 删除对应容器的网络
                                dockerService.deleteDockerById(c);
                                String containerIp = ipService.getContainerIp(c);
                                ipService.deleteIP(clusterId, cName, c, containerIp);
                            });
                }
        );
        clustersInfo.remove(clusterId);
    }

    /**
     * @description 删除指定容器
     * @param clusterId
     * @param containerName
     */
    @Override
    public void removeContainersByClusterIdAndContainerName(long clusterId, String containerName) {
        LOG.debug("删除集群 [%l] 的容器 [%s]", clusterId, containerName);
        if(!clustersInfo.containsKey(clusterId)) {
            return;
        }
        clustersInfo.get(clusterId).get(containerName).forEach(
                c -> {
                    dockerService.deleteDockerById(c);
                    String containerIp = ipService.getContainerIp(c);
                    ipService.deleteIP(clusterId, containerName, c, containerIp);
                });
        clustersInfo.get(clusterId).remove(containerName);
    }

    /**
     * 获取ovs列表
     * @return
     */
    @Override
    public List<OvsBridge> getOvsBridges() {
        LOG.debug("查询ovs网桥");
        return ovsService.getOvsBridges();
    }

    /**
     * 根据容器id，获取容器的网络信息
     * @param containerIds
     * @return
     */
    @Override
    public List<NetworkInfo> getNetworkTraffic(Set<String> containerIds) {
        List<NetworkInfo> networkInfos = new ArrayList<>();
        containerIds.forEach(cid -> {
            try {
                NetworkInfo networkInfo = tcService.networkUsage(cid);
                networkInfo.setContainerId(cid);
                networkInfos.add(networkInfo);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        return networkInfos;
    }

    @Override
    public NetworkInfo getNetworkInfoByContainerId(String containerId) {
        try {
            return tcService.networkUsage(containerId);
        } catch (InterruptedException e) {
            // 查询已经中断
            LOG.info("[" + containerId + "]网络监控以中断");
        }
        return null;
    }

    // TODO: 临时的服务接口，待移除
    @Override
    public String createNetwork(long clusterId, String appName) {
        if(!clustersInfo.containsKey(clusterId)) {
            return "failed!";
        }

        if(!clustersInfo.get(clusterId).containsKey(appName)) {
            return "failed!";
        }

        Set<String> containerIdsByClusterId = getContainerIdsByClusterId(clusterId, appName);

        ovsService.addBridge(appName + "br");
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        int i = 2;
        for (String cid : containerIdsByClusterId) {
            ovsDockerService.addPort(appName + "br", "eth1", cid, "10.0.1." + (i++) + "/24");
        }

        return "success!";
    }

    /**
     * 查询边缘服务器CPU情况
     * @return
     */
    @Override
    public String cpu() {
        return osUtils.cpuUsage() + "%";
    }

    /**
     * 查询边缘服务器的内存使用情况
     * @return
     */
    @Override
    public String mem() {
        return osUtils.memoryUsage() + "%";
    }

    /**
     * 查询Docker cpu使用情况
     * @param containerId
     * @return
     */
    @Override
    public String cpu(String containerId) {
        return osUtils.cpuUsage(containerId) + "%";
    }


    @Override
    public String mem(String containerId) {
        return osUtils.memoryUsage(containerId) + "%";
    }

    // TODO: 动态添加新节点到服务中,可以考虑这种实现，但是感觉不太现实，集群启动了，其中的服务应该是固定了的，添加新的节点也很难使用
    //  但是也可能存在一种情况，根据节点情况，启动不同的服务，也有存在的道理。
}